#!/usr/bin/perl
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
#  
#  
# Licensed Materials - Property of IBM 
#  
# (C) COPYRIGHT International Business Machines Corp. 2000,2004 
# All Rights Reserved 
#  
# US Government Users Restricted Rights - Use, duplication or 
# disclosure restricted by GSA ADP Schedule Contract with IBM Corp. 
#  
# IBM_PROLOG_END_TAG 
#
# @(#)59   1.20   src/csm/install/setupconsole, setup, csm_rameh, rameh0431a 3/18/04 23:00:39

#--------------------------------------------------------------------------------

=head1    setupconsole

	Sets up the console early in the boot

        Development notes:

        The html view of the pod headers in this file can be updated
        by running the command:  

                  tidypod <filename>

	The html veiw of the pod headers will be in the file ./<filename>.html
	and can be viewed with a browser.

	The tidy formatted file will be in <filename>.tdy

	Be sure to run tidypod and copy the tdy file to <filename> before checking
	in changes.

=cut

#--------------------------------------------------------------------------------

#use strict;

# BEGIN is executed first, regardless of the loading order of
# other program modules, such as NodeUtils.pm.

BEGIN
{
    use File::Basename;    # The path of the command

    # Assumes msgmaps are in same dir as application
    # This can be meaningful for using csm.core code like
    # runcmd, or using messaging and logging from /csminstall

    ($::Bin) = dirname($0);

    $::MSGCAT     = 'csmInstall.cat';
    $::MSGMAPPATH = $::Bin;
    $::MSGSET     = 'csminstall';
    $::MSGCAT     = 'csmInstall.cat';

    $::csmpm = $ENV{'CSM_PM'} ? $ENV{'CSM_PM'} : '/opt/csm/pm';
}

# Look for perl modules first in the same dir
# as the command,then look in "/opt/csm/pm"

use lib ($::Bin, $::csmpm);

use Getopt::Std;
use NodeUtils;
use CSMDefs;
use InstallUtils;

#--------------------------------------------------------------------------------

=head3	dprint

	Debug print

        Notes:

=cut

#--------------------------------------------------------------------------------

sub dprint {
    my ($dstring) = @_;

    if ( $::VERBOSE ) {
        ($sec, $min, $hour, $mday, $month, $year) = (localtime)[0,1,2,3,4,5];
        $month += 1;
        $year += 1900;

        $timestamp = sprintf( "%02d/%02d/%d %02d:%02d:%02d", $month, $mday, $year, $hour, $min, $sec );

        print DEBUGFILE "$timestamp $dstring\n";
    }
}

#--------------------------------------------------------------------------------

=head3	initialize	

	Init the global data

        Notes:

=cut

#--------------------------------------------------------------------------------

sub initialize
{

    $ENV{'CT_MANAGEMENT_SCOPE'} =
      1;    # set local scope because we only want classes on the mgmt svr
    $ENV{'CT_SESSION_SCOPE'} =
      1;    # todo: remove when lsrsrc-api converts to above

    # Global variables

    # get the definitions we need for this command
    #    get_OSDefs uses the attrs of the management server to identify
    #    the correct definitions.
    #    Don't pass in CSMVersion because we cannot query the rpm command
    #    while we are already in a subshell of the rpm %post.
    my %OSDefs =
      InstallUtils->get_OSDefs(
                               $::PLTFRM,
                               InstallUtils->get_DistributionName,
                               InstallUtils->get_DistributionVersion,
                               InstallUtils->get_PkgArchitecture
                               );

    $::console_device        = '';    # Console Serial Device
    $::console_device_number = '';    # Console Serial Device number
    $::console_speed         = '';    # Console Serial Speed
    $::cfg_changed    = 0;     # Flag used to tell caller we changed something
    $::install_method = '';    # Install Method


}

#--------------------------------------------------------------------------------

=head3	get_config_info

	Read the configinfo file to get the ConsoleSerialDevice attribute for
	this node.  If the file does not exist, assume the CSM RPMs were installed 
	by hand and not through updatenode or a full install from the mgmt 
	server -- default the device to "ttyS0".  

	If the attribute does not exist in the file, or has a null value, assume
	that the file is from an early CSM 1.2 system, which hardcoded the value
	"ttyS1" for the device.

        Notes:

=cut

#--------------------------------------------------------------------------------

sub get_config_info
{
    unless (-f $::CFGINFOFILE)
    {
        $::console_device        = "ttyS0";
        $::console_device_number = "0";
        return 0;
    }

    # Look for both ConsoleSerialDevice and ConsoleSerialSpeed
    #
    my @cfgdata = NodeUtils->runcmd("$::GREP ConsoleSerial $::CFGINFOFILE", -1);
    my $dataline;
    foreach $dataline (@cfgdata)
    {
        my ($attr, $val) = split("=", $dataline);
        if ($attr eq "ConsoleSerialDevice")
        {
            $::console_device        = $val;
            $::console_device_number = $::console_device;
            $::console_device_number =~ s/\D//g;    # remove all non-digits
        }
        elsif ($attr eq "ConsoleSerialSpeed")
        {
            $::console_speed = $val;
        }
    }
    if ($::console_device eq '')
    {
        $::console_device        = "ttyS1";
        $::console_device_number = "1";
    }

    my $installmethoddata =
      NodeUtils->runcmd("$::GREP InstallMethod $::CFGINFOFILE", -1);
    ($attr, $val) = split("=", $installmethoddata);
    if ($attr eq "InstallMethod")
    {
        $::install_method = $val;
        chomp $::install_method;
    }

    dprint "console device is $::console_device";
    dprint "console device number is $::console_device_number";
    dprint "console speed is $::console_speed";
    dprint "install method is $::install_method";
    
    return 0;
}

#--------------------------------------------------------------------------------

=head3	process_bootLoader

	Process the /etc/lilo.conf or the /etc/grub.conf file.  Add the
	following two lines to the file either after the "default=" line
	or, if a "default=" line doesn't exist, add the lines before the first 
	"image=" line.


        Notes:

=cut

#--------------------------------------------------------------------------------

sub process_bootLoader
{

    $bootLoader    = shift;
    $bootLoaderbak = $bootLoader . ".bak";

    $grub_addline1 =
      "serial --unit=$::console_device_number --speed=$::console_speed";
    $grub_remline1 = "serial --unit=";
    $grub_addline2 = "terminal --timeout=0 serial";
    $grub_remline2 = "terminal --timeout=0 serial";

    $lilo_addline1 =
      "serial = $::console_device_number," . $::console_speed . "n8";
    $lilo_remline1 = "serial = ";

    # $lilo_addline2 = "append = \"console=tty1 console=$::console_device,$::console_speed\"";
    $lilo_addline2 =
      "append = \"console=$::console_device,$::console_speed console=tty1\"";
    $lilo_remline2 = "append = \"console=tty";

    $old_remline1 = "serial = ";
    $old_remline2 = "append = \"console=tty";
    $old_remline3 = "append = \"console=";

    if ($bootLoader =~ /grub/)
    {
        $addline1 = $grub_addline1;
        $remline1 = $grub_remline1;
        $addline2 = $grub_addline2;
        $remline2 = $grub_remline2;
    }
    elsif ($bootLoader =~ /lilo/)
    {
        $addline1 = $lilo_addline1;
        $remline1 = $lilo_remline1;
        $addline2 = $lilo_addline2;
        $remline2 = $lilo_remline2;
    }

    # Make sure the boot loader config file exists as a regular file
    #
    unless (-f $bootLoader)
    {
        return 1;
    }

    # Open the current bootloader config file for read only, then
    # read into an array and close the file.
    #
    unless (open(BOOTLOADER, "<$bootLoader"))
    {
        NodeUtils->message('W', 'EMsgCANT_OPEN_FOR_CONSOLE', $bootLoader);
        return;
    }

    my @bl = <BOOTLOADER>;
    close BOOTLOADER;

    # If the ConsoleSerialDevice is set to NONE, we need to remove the serial
    # config entries from the file, if present.
    #
    if ($::console_device eq 'NONE')
    {

        # Look for the bootloader entries in the config file
        #
        if ((grep /^$old_remline1|^$old_remline3/, @bl) > 0)
        {

            # We found serial entries in the config file.  Need to back up
            # the existing file, then re-write the config without these
            # entries
            #
            NodeUtils->runcmd("/bin/cp -p $bootLoader $bootLoaderbak");

            unless (open(BOOTLOADER, ">$bootLoader"))
            {
                NodeUtils->message('W', 'EMsgCANT_OPEN_FOR_CONSOLE',
                                   $bootLoader);
                return;
            }

            foreach $line (@bl)
            {
                chomp($line);
                if ($line =~ /^$old_remline1|^$old_remline3/)
                {

                    # Comment this line out
                    #
                    NodeUtils->message('V', 'IMsgREMOVING_LINE', $bootLoader,
                                       $line);
                    print BOOTLOADER "#$line\n";
                    $::cfg_changed = 1;
                }
                else
                {
                    print BOOTLOADER "$line\n";
                }
            }
            close BOOTLOADER;
        }
    }
    else
    {

        # ConsoleSerialDevice is set to a tty spec.  We need to scan
        # the config file to see if both the exact entries we need to add
        # are already in the file.  If not, they need to be added.
        #
        if (!(((grep /^$addline1/, @bl) > 0) && ((grep /^$addline2/, @bl) > 0)))
        {

            # BOTH of the required entries were not found in the config
            # file, so we need to add them
            #
            NodeUtils->runcmd("/bin/cp -p $bootLoader $bootLoaderbak");

            unless (open(BOOTLOADER, ">$bootLoader"))
            {
                NodeUtils->message('W', 'EMsgCANT_OPEN_FOR_CONSOLE',
                                   $bootLoader);
                return;
            }

            $added_lines = "";

            foreach $line (@bl)
            {
                if ($line =~ /default\s*=/)
                {
                    NodeUtils->message('V', 'IMsgADDING_LINE', $bootLoader,
                                       $addline1);
                    NodeUtils->message('V', 'IMsgADDING_LINE', $bootLoader,
                                       $addline2);
                    print BOOTLOADER $line;
                    print BOOTLOADER "$addline1\n";
                    print BOOTLOADER "$addline2\n";
                    $added_lines   = "true";
                    $::cfg_changed = 1;
                }
                elsif (($line =~ /image\s*=/) && (!$added_lines))
                {
                    NodeUtils->message('V', 'IMsgADDING_LINE', $bootLoader,
                                       $addline1);
                    NodeUtils->message('V', 'IMsgADDING_LINE', $bootLoader,
                                       $addline2);
                    print BOOTLOADER "$addline1\n";
                    print BOOTLOADER "$addline2\n";
                    print BOOTLOADER $line;
                    $added_lines = "true";
                    $cfg_changed = 1;
                }
                elsif (   ($line =~ /^$remline1/)
                       || ($line =~ /^$remline2/)
                       || ($line =~ /^$old_remline1/)
                       || ($line =~ /^$old_remline2/))
                {
                    NodeUtils->message('V', 'IMsgREMOVING_LINE', $bootLoader,
                                       $line);
                    chomp($line);
                    if (($line ne $addline1) && ($line ne $addline2))
                    {
                        print BOOTLOADER "#$line\n";
                    }
                }
                else
                {
                    print BOOTLOADER $line;
                }
            }

            close BOOTLOADER;
        }
    }
}

#--------------------------------------------------------------------------------

=head3	process_inittab

	Process the /etc/inittab file.

        Notes:

=cut

#--------------------------------------------------------------------------------

sub process_inittab
{
    dprint "Starting process_inittab";

    $agettyline =
      "s1:345:respawn:/sbin/agetty -h $::console_speed $::console_device xterm";
    $searchline          = "/sbin/agetty";
    $searchline_mingetty = "/sbin/mingetty";
    $inittab             = "/etc/inittab";
    $addlines            = "

# CSM RPM post installation added this entry
# to allow serial console support for this node
$agettyline
";

    # Open the inittab file and read into an array
    #
    dprint "Opening inittab file";
    unless (open(INITTAB, "<$inittab"))
    {
        NodeUtils->message('E1', 'EMsgCANT_OPEN_FOR_CONSOLE', $inittab);
        return;
    }

    dprint "Opening inittab output file";
    unless (open(INITTAB_OUT, ">$inittab.$$"))
    {
        NodeUtils->message('E1', 'EMsgCANT_OPEN_FOR_CONSOLE', $inittab . $$);
        return;
    }

    dprint "Reading inittab into array";
    my @init_tab = <INITTAB>;

    dprint "Closing inittab";
    close INITTAB;

    # If the ConsoleSerialDevice is set to NONE, we need to remove the agetty
    # entry from the file, if present.
    #
    if( $::console_device eq 'NONE' ) 
    {
        dprint "ConsoleSerialDevice = NONE";

        # Look for an agetty entry in the inittab file
        #
        if ((grep /$searchline/, @init_tab) > 0)
        {
            dprint "Found an agetty in inittab";

            # We found an agetty entry in the inittab file.  Need to
            # scan the file and remove the one that corresponds to
            # our console device.
            #

            foreach $line (@init_tab)
            {
                chomp($line);
                dprint "Current inittab line is $line";
                
                if( ( $line =~ /$searchline.*$::console_device/ ) && ( $line !~ /^\s*#/ ) )
                {
                    dprint "Uncommented agetty line matching our device found - removing";

                    # Comment this line out
                    #
                    NodeUtils->message('V', 'IMsgREMOVING_LINE', $inittab,
                                       $line);
                    print INITTAB_OUT "#$line\n";
                    $::cfg_changed = 1;
                }
                else
                {
                    print INITTAB_OUT "$line\n";
                }
            }
            dprint "Closing inittab output file";
            close INITTAB_OUT;
            NodeUtils->runcmd("$::MV $inittab.$$ $inittab");
        }
    }
    else
    {
        # ConsoleSerialDevice is set to a tty spec.
        #
        
        dprint "ConsoleSerialDevice is tty";
        
        if( $::install_method eq 'autoyast' ) {
            dprint "Install method is autoyast";
            
            # autoyast installs require special processing.  autoyast will create
            # entries in the inittab file based on kernel parameters passed in to
            # the yastinstall.pxe file, which means this process must not do the
            # same (as it would result in two agetty entries in inittab for the same
            # serial port).  We need to detect whether setupconsole is being run by
            # an install or update process so we know whether to create an entry or not.
            # To do this, we'll check our parent's process.  If the parent is
            # updatenode.client, we'll proceed with adding the entry.  Otherwise, we'll
            # simply return.
            #
            my $ppid = getppid;
            dprint "ppid is $ppid";

            # get the information from the /proc filesystem.  The format is different
            # for AIX, but consistent in SLES and RedHat Linux.  Should be OK, since an
            # installmethod of "autoyast" won't apply to AIX installs.
            # 
            open( PPID_CMD, "</proc/$ppid/cmdline" );
            my $ppid_cmd = <PPID_CMD>;
            close PPID_CMD;

            dprint "ppid command is $ppid_cmd";

            if( $ppid_cmd !~ /updatenode.client/ ) {
                dprint "Not called from updatenode.client - returning";
                close INITTAB_OUT;
                return;
            }
        }
            
        # We need to scan the inittab file to see if the exact entry we need to add
        # is already in the file.  If not, it needs to be added.
        #
        $agetty_written = 0;

        dprint "Scanning inittab";

        foreach $line (@init_tab) {
            chomp($line);

            dprint "Current line is $line";
            
            # check if the line is an uncommented agetty line with our
            # serial port
            #
            if( ( $line =~ /$searchline.*$::console_device/ ) && ( $line !~ /^\s*#/ ) ) {

                dprint "Uncommented agetty matching our console device found";
                # check if the line matches the line we need to add.  If it
                # matches, and we haven't already added the line, add it now.
                # Don't set cfg_changed, since we're writing a line that's
                # already in the file.
                # 
                if( ( $line eq $agettyline ) && ( !$agetty_written ) ) {
                    dprint "agetty matching required CSM agetty found, not yet written";
                    dprint "Writing agetty to inittab";
                    print INITTAB_OUT "$line\n";
                    $agetty_written = 1;
                } else {
                    dprint "Removing duplicate agetty";
                    # the line doesn't match the line we need to add, but is using
                    # the same serial port - need to comment this line out (cannot
                    # have two or more agetty's running on the same port).
                    #
                    NodeUtils->message( 'V', 'IMsgREMOVING_LINE', $inittab, $line );
                    print INITTAB_OUT "#$line\n";
                    $::cfg_changed = 1;
                }
            } elsif( ( $line =~ /$searchline_mingetty/ ) && ( $line =~ /^\s*#/ ) ) {
                dprint "Commented mingetty found - uncommenting";
                # If we find a mingetty line commented out, uncomment it.
                $line =~ s/^\s*#//;
                print INITTAB_OUT "$line\n";
                $::cfg_changed = 1;
            } else {
                dprint "Writing non-getty line";
                # not a line we're interested in - just write it out.
                #
                print INITTAB_OUT "$line\n";
            }
        }

        # if the agetty entry hasn't been written yet, do it now.
        #
        if( !$agetty_written ) {
            dprint "Adding CSM agetty";
            print INITTAB_OUT $addlines;
            $::cfg_changed = 1;
        }        
        
        dprint "Closing and copying inittab";
        close INITTAB_OUT;
        NodeUtils->runcmd("$::MV $inittab.$$ $inittab");
    }
}

#--------------------------------------------------------------------------------

=head3	Main	

        Notes:

=cut

#--------------------------------------------------------------------------------

{   # main

    $::VERBOSE = ($ARGV[0] eq "-v") || ($ARGV[0] eq "-V");

    if( $::VERBOSE ) {
        open DEBUGFILE, ">/var/log/csm/setupconsole.log";

        # Disable buffering on the debug file, to be sure all output
        # is printed when it's generated
        #
        $debug_file = select ( DEBUGFILE );
        $| = 1;
        select ( $debug_file );
    }

    dprint "Initializing";
    &initialize;

    dprint "Calling get_config_info";
    &get_config_info();

    dprint "Calling process_bootLoader for lilo.conf";
    &process_bootLoader('/etc/lilo.conf');

    # Don't process /etc/grub.conf.  We cannot get grub output to go to both
    # local console and serial port, so we'll choose just local console.
    #&process_bootLoader('/etc/grub.conf');

    dprint "Calling process_inittab";
    &process_inittab();

    dprint "Exiting with code $::cfg_changed";

    if( $::VERBOSE ) {
        close DEBUGFILE;
    }
    
    exit $::cfg_changed;

}
